Adding a New Service¶
Repeatable pattern for adding a new self-hosted service to the homelab. Based on the Phase 1 pattern (n8n).
Last updated: 2026-04-10
Prerequisites¶
- Service has a Docker image
- You have a subdomain in mind (e.g.,
app.exzentcg.com) - You know what port the service listens on
Step 1 — Create the LXC¶
From Proxmox WebUI → Create CT:
| Setting | Value |
|---|---|
| CT ID | Next available (103, 104, ...) |
| Hostname | <service-name> |
| Template | debian-12-standard |
| Root disk | 8 GB (adjust per service) |
| CPU | 1 core |
| Memory | 512–1024 MB (adjust per service) |
| Network | Bridge: vmbr0, Static IP: 192.168.0.<next>/24, Gateway: 192.168.0.1 |
| DNS | 192.168.0.1 |
| Features | Nesting ✅ (required for Docker) |
Check Start after created → Finish.
Step 2 — Install Docker and deploy¶
# SSH or console into the new LXC
apt update && apt upgrade -y
apt install curl -y
curl -fsSL https://get.docker.com | sh
# Create service directory
mkdir -p /opt/<service-name> && cd /opt/<service-name>
# Create docker-compose.yml
nano docker-compose.yml
# Create .env if needed
nano .env
chmod 600 .env
# Start
docker compose up -d
docker compose ps
docker compose logs --tail 30 <service>
Step 3 — Configure LXC firewall¶
From the Proxmox host, create /etc/pve/firewall/<CTID>.fw:
cat > /etc/pve/firewall/<CTID>.fw << 'EOF'
[OPTIONS]
enable: 1
[RULES]
IN ACCEPT -source +edge_gw -p tcp -dport <SERVICE_PORT> # Only edge-gateway can reach it
IN ACCEPT -source +admin_desktop -p tcp -dport 22 # SSH from admin
IN DROP # Block everything else
OUT ACCEPT -dest +router_gw -p udp -dport 53 # DNS
OUT ACCEPT -dest +router_gw -p tcp -dport 53 # DNS
OUT DROP -dest +lan_subnet # Lateral movement prevention
OUT ACCEPT -p tcp -dport 443 # HTTPS APIs (if needed)
EOF
Enable firewall on the container: Proxmox WebUI → CT → Firewall → Options → Firewall: Yes.
Verify:
# From inside the new LXC
curl -s --connect-timeout 3 http://192.168.0.16 -o /dev/null -w "HTTP %{http_code}\n"
# Expected: HTTP 000 (LAN blocked)
curl -s --connect-timeout 3 https://192.168.0.200:8006 -o /dev/null -w "HTTP %{http_code}\n"
# Expected: HTTP 000 (Proxmox blocked)
Step 4 — Update edge-gateway firewall¶
Add outbound rule for edge-gateway to reach the new service. Edit /etc/pve/firewall/101.fw on the Proxmox host:
OUT ACCEPT -dest 192.168.0.<IP> -p tcp -dport <SERVICE_PORT> # Proxy to <service-name>
Place this before the OUT DROP -dest +lan_subnet line.
Step 5 — Add NPM proxy host¶
- Open NPM admin panel:
http://192.168.0.51:81 - Proxy Hosts → Add Proxy Host:
| Field | Value |
|---|---|
| Domain Names | <subdomain>.exzentcg.com |
| Scheme | http |
| Forward Hostname / IP | 192.168.0.<IP> |
| Forward Port | <SERVICE_PORT> |
| Websockets Support | ✅ if the app uses WebSockets |
| SSL Certificate | None |
Step 6 — Add Cloudflare tunnel route¶
Cloudflare → Zero Trust → Networks → Tunnels → exzentcg-homelab → Published application routes → Add:
| Field | Value |
|---|---|
| Subdomain | <subdomain> |
| Domain | exzentcg.com |
| Path | (empty) |
| Service Type | HTTP |
| URL | localhost:80 |
| HTTP Host Header | <subdomain>.exzentcg.com |
Cloudflare auto-creates the DNS CNAME (proxied).
Step 7 — Add Cloudflare Access policy¶
Zero Trust → Access → Applications → Add → Self-hosted:
| Field | Value |
|---|---|
| Application name | <service-name> |
| Subdomain | <subdomain> |
| Domain | exzentcg.com |
| Session Duration | 24 hours |
Add policy:
- Name: owner-only (reuse existing)
- Action: Allow
- Selector: Emails → exzensg@gmail.com
If the service needs unauthenticated webhook endpoints, create a second application with a path prefix and a Bypass + Everyone policy (same pattern as n8n-webhooks).
Step 8 — Test end-to-end¶
- Visit
https://<subdomain>.exzentcg.com - Cloudflare Access login should appear
- Log in → service should load
- Test from another device / incognito to confirm access control
Step 9 — Snapshot¶
pct snapshot <CTID> initial-deploy --description "<service-name> initial deployment"
Step 10 — Document¶
- Add credentials to
00_secrets/Credentials Index.md - Create
02_setup_logs/Phase <N> Actions.mdwith deployment notes - Update
01_planning/Architecture Diagram.mdwith the new service - Add health check commands to
Phase 1 Health Checks.md
Checklist¶
- [ ] LXC created with static IP and Docker
- [ ] Service deployed and running
- [ ] LXC firewall rules applied and tested
- [ ] edge-gateway firewall updated to allow proxy
- [ ] NPM proxy host configured
- [ ] Cloudflare tunnel route added
- [ ] Cloudflare Access policy applied
- [ ] End-to-end test passed
- [ ] Snapshot taken
- [ ] Documentation updated